#ifndef TEXTPREPROCESSOR_URL_HPP
#define TEXTPREPROCESSOR_URL_HPP

#include <iostream>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <vector>
#include <stdexcept>
#include <unordered_map>
#include "Url.hpp"

#define RELATIVE_SCHEME "http"
#define HEXDEC(c) ( \
	c >= '0' && c <= '9' ? c - '0' : ( \
		c >= 'a' && c <= 'f' ? c - 'a' + 10 : ( \
			c >= 'A' && c <= 'F' ? c - 'A' + 10 : 0 \
		) \
	) \
)
#define URI_IS_SUBDELIMS(c) ( \
	c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || \
	c == '*' || c == '+' || c == ',' || c == ';' || c == '=' \
)
#define URI_IS_GENDELIMS(c) ( \
	c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@' \
)
#define URI_IS_UNRESERVERD(c) ( \
	c == '%' || isalpha(c) || isdigit(c) || c == '-' || c == '.' || c == '_' || c == '~' \
)

#ifdef URL_PARSE_PEDANTIC
	#define VALIDATE_FRAG(c)   (URI_IS_UNRESERVERD(c) || URI_IS_SUBDELIMS(c) || c == '@' || c == ':' || c == '/' || c == '?')
	#define VALIDATE_HOST(c)   (URI_IS_UNRESERVERD(c) || URI_IS_SUBDELIMS(c))
	#define VALIDATE_SCHEME(c) (isalpha(c) || c == '+' || c == '.' || c == '-')
	#define VALIDATE_AUTH(c)   (URI_IS_UNRESERVERD(c) || URI_IS_SUBDELIMS(c))
	#define VALIDATE_PATH(c)   (URI_IS_UNRESERVERD(c) || URI_IS_SUBDELIMS(c) || c == '@' || c == ':' || c == '/')
	#define VALIDATE_QUERY(c)  (URI_IS_UNRESERVERD(c) || URI_IS_SUBDELIMS(c) || c == '@' || c == ':' || c == '?' || c == '/')
	#define IS_QUERY_SEP(c)    (c == ';' || c == '&')
#else
	#define VALIDATE_FRAG(c)   (true)
	#define VALIDATE_HOST(c)   (c != '#' && c != '/' && c != '?' && c != '@' && c != ':')
	#define VALIDATE_SCHEME(c) (isalpha(c) || c == '+' || c == '.' || c == '-')
	#define VALIDATE_AUTH(c)   (c != '#' && c != '/' && c != '?' && c != '@' && c != ':')
	#define VALIDATE_PATH(c)   (c != '#' && c != '?')
	#define VALIDATE_QUERY(c)  (c != '#')
	#define IS_QUERY_SEP(c)    (c == ';' || c == '&')
#endif

#define MAX_SCHEME_LENGTH      16
#define MIN_SCHEME_LENGTH      1
#define MAX_USER_LENGTH        128
#define MIN_USER_LENGTH        1
#define MAX_PASS_LENGTH        128
#define MIN_PASS_LENGTH        1
#define MAX_HOST_LENGTH        256
#define MIN_HOST_LENGTH        3
#define MAX_PORT_LENGTH        5
#define MIN_PORT_LENGTH        1
#define MAX_PATH_LENGTH        1024
#define MIN_PATH_LENGTH        1
#define MIN_QUERY_PART         1
#define MAX_QUERY_PART         2048
#define MAX_FRAGMENT_LENGTH    256
#define MIN_FRAGMENT_LENGTH    1

using namespace std; 
class Url {
	public:
		typedef struct URIQuery {
			string name; 
			string value; 
			size_t hash; 
		} URIQuery; 
		
		typedef struct URI {
			char scheme[MAX_SCHEME_LENGTH + 1]; 
			unsigned short scheme_length; 
			
			char user[MAX_USER_LENGTH + 1]; 
			unsigned short user_length; 
			
			char pass[MAX_PASS_LENGTH + 1]; 
			unsigned short pass_length; 
			
			char host[MAX_HOST_LENGTH + 1]; 
			unsigned short host_length; 
			
			char path[MAX_PATH_LENGTH + 1]; 
			unsigned short path_length; 
			
			vector<URIQuery> query; 
			
			char fragment[MAX_FRAGMENT_LENGTH + 1]; 
			unsigned short fragment_length; 
			
			unsigned short port; 
		} URI; 
	
	private: 
		URI uri; 
		hash<string> hasher; 
		
		enum {
			S_SCHEME = 0, 
			S_USER, 
			S_PASS, 
			S_HOST, 
			S_PORT, 
			S_PATH, 
			S_QUERY, 
			S_FRAGMENT, 
			S_END, 
		}; 
		
		enum {
			SCHEME = 0, 
			USER, 
			PASS, 
			HOST, 
			PORT, 
			PATH, 
			FRAGMENT, 
		}; 
		
		inline void _initStruct() {
			uri.scheme[0] = 0; 
			uri.scheme_length = 0; 
			uri.user[0] = 0; 
			uri.user_length = 0; 
			uri.pass[0] = 0; 
			uri.pass_length = 0; 
			uri.host[0] = 0; 
			uri.host_length = 0; 
			uri.path[0] = 0; 
			uri.path_length = 0; 
			uri.fragment[0] = 0; 
			uri.fragment_length = 0; 
			uri.query.clear(); 
			uri.port = 0; 
		}
		
		inline URIQuery &_addQueryKey(vector<URIQuery> &query, const char *name, const char *value) {
			query.resize(query.size() + 1); 
			URIQuery &q = query[query.size() - 1]; 
			q.name  = name; 
			q.value = value; 
			q.hash  = hasher(name); 
			return query[query.size() - 1]; 
		}
		
		inline void _deleteQueryKey(vector<URIQuery> &query, const char *name, bool all = false) {
			size_t hash = hasher(name); 
			for (size_t i = 0; i < query.size(); ++i) {
				if (query[i].hash == hash) {
					query.erase(query.begin() + i); 
					if (!all)
						break; 
				}
			}
		}
		
		inline const string _getQueryKey(vector<URIQuery> &query, const char *name) {
			size_t hash = hasher(name); 
			for (size_t i = query.size(); i-- > 0; )
				if (query[i].hash == hash)
					return query[i].value; 
			return NULL; 
		}
	public: 
		static const char hex_chars[16]; 
		
		Url() {
			_initStruct(); 
		}
		
		Url(Url &url) {
			copy(url); 
		}
		
		inline void copy(Url &url) {
			strncpy(uri.scheme, url.getScheme(), url.getSchemeLength()); 
			uri.scheme_length = url.getSchemeLength(); 
			uri.scheme[uri.scheme_length] = 0; 
			
			strncpy(uri.user, url.getUser(), url.getUserLength()); 
			uri.user_length = url.getUserLength(); 
			uri.user[uri.user_length] = 0; 
			
			strncpy(uri.pass, url.getPassword(), url.getPasswordLength()); 
			uri.pass_length = url.getPasswordLength(); 
			uri.pass[uri.pass_length] = 0; 
			
			strncpy(uri.host, url.getHost(), url.getHostLength()); 
			uri.host_length = url.getHostLength(); 
			uri.host[uri.host_length] = 0; 
			
			strncpy(uri.path, url.getPath(), url.getPathLength()); 
			uri.path_length = url.getPathLength(); 
			uri.fragment[uri.path_length] = 0; 
			
			strncpy(uri.fragment, url.getFragment(), url.getFragmentLength()); 
			uri.fragment_length = url.getFragmentLength(); 
			uri.fragment[uri.fragment_length] = 0; 
			
			uri.query = vector<URIQuery>(url.getQuery()); 
			uri.port = url.getPort(); 
		}
		
		void parse(const char *url, unsigned int len); 
		string toString(const char *separator = ";"); 
		
		const string &operator[](const char *name) const {
			size_t hash = hasher(name); 
			for (size_t i = uri.query.size(); i-- > 0; )
				if (uri.query[i].hash == hash)
					return uri.query[i].value; 
			throw std::out_of_range("Key in query not found!"); 
		}
		
		string &operator[](const char *name) {
			size_t hash = hasher(name); 
			for (size_t i = uri.query.size(); i-- > 0; )
				if (uri.query[i].hash == hash)
					return uri.query[i].value; 
			return _addQueryKey(uri.query, name, "").value; 
		}
		
		inline void deleteQueryKey(const char *name, bool all = false) {
			_deleteQueryKey(uri.query, name, all); 
		}
		
		inline bool isQueryKeyExists(const char *name) {
			size_t hash = hasher(name); 
			for (size_t i = uri.query.size(); i-- > 0; )
				if (uri.query[i].hash == hash)
					return true; 
			return false; 
		}
		
		inline void setScheme(const char *val, unsigned int len) {
			strncpy(uri.scheme, val, MAX_SCHEME_LENGTH); 
			uri.scheme_length = len; 
		}
		
		inline void setUser(const char *val, unsigned int len) {
			strncpy(uri.user, val, MAX_USER_LENGTH); 
			uri.user_length = len; 
		}
		
		inline void setPassword(const char *val, unsigned int len) {
			strncpy(uri.pass, val, MAX_PASS_LENGTH); 
			uri.pass_length = len; 
		}
		
		inline void setHost(const char *val, unsigned int len) {
			strncpy(uri.host, val, MAX_HOST_LENGTH); 
			uri.host_length = len; 
		}
		
		inline void setPath(const char *val, unsigned int len) {
			strncpy(uri.path, val, MAX_PATH_LENGTH); 
			uri.path_length = len; 
		}
		
		inline void appendPath(const char *val, unsigned int len) {
			unsigned int new_len = uri.path_length + len; 
			new_len = new_len > MAX_PATH_LENGTH ? MAX_PATH_LENGTH : new_len; 
			if (new_len == uri.path_length || uri.path_length == MAX_PATH_LENGTH)
				return; 
			strncpy(uri.path + uri.path_length, val, MAX_PATH_LENGTH); 
			uri.path_length = new_len; 
		}
		
		inline void setFragment(const char *val, unsigned int len) {
			strncpy(uri.fragment, val, MAX_FRAGMENT_LENGTH); 
			uri.fragment_length = len; 
		}
		
		inline void setPort(unsigned int port) {
			uri.port = port; 
		}
		
		inline const char *getScheme() {
			return uri.scheme; 
		}
		
		inline unsigned int getSchemeLength() {
			return uri.scheme_length; 
		}
		
		inline const char *getUser() {
			return uri.user; 
		}
		
		inline unsigned int getUserLength() {
			return uri.user_length; 
		}
		
		inline const char *getPassword() {
			return uri.pass; 
		}
		
		inline unsigned int getPasswordLength() {
			return uri.pass_length; 
		}
		
		inline const char *getHost() {
			return uri.host; 
		}
		
		inline unsigned int getHostLength() {
			return uri.host_length; 
		}
		
		inline const char *getPath() {
			return uri.path; 
		}
		
		inline unsigned int getPathLength() {
			return uri.path_length; 
		}
		
		inline const char *getFragment() {
			return uri.fragment; 
		}
		
		inline unsigned int getFragmentLength() {
			return uri.fragment_length; 
		}
		
		inline unsigned short getPort() {
			return uri.port; 
		}
		
		inline vector<URIQuery> &getQuery() {
			return uri.query; 
		}
		
		inline static size_t _urlEncodeOverhead(const string &str, bool slash = false); 
		static string &encode(string &str); 
		static string encodeDup(const string &str); 
		static size_t _urlEncodeUnderhead(const string &str); 
		static string &decode(string &str); 
		static string decodeDup(const string &str, bool calc_new = false); 
		static void appendToUrl(string &url, const string &part, bool slash = false); 
		
		inline static size_t _urlEncodeOverhead(const char *str, size_t len, bool slash = false); 
		static char *encode(char *str, size_t len, size_t *new_length, bool *need_to_free = NULL); 
		inline static size_t _urlEncodeUnderhead(const char *str, size_t len); 
		static char *decode(char *str, size_t len, size_t *new_len, bool dup = false, bool calc_new = false); 
		static void appendToUrl(string &url, const char *part, size_t len, bool slash = false); 
		
		~Url() {
			
		}
}; 

#endif // TEXTPREPROCESSOR_URL_HPP

